/*
 * Copyright (c) 2010-2013 Ruben Chakhmakhchyan. All rights reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Eclipse Public License
 * Version 1.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://opensource.org/licenses/EPL-1.0 and read it before using this file.
 */

#include "stdafx.h"
#include "commontypes.h"
#include "wia_model_impl.h"
#include "wiacommon.h"
#include "datacallback.h" // WIA 1.0
#include "transfercallback.h" // WIA 2.0
#include "result_codes.h"

// initialize static members
char WiaModel::m_modelTypeName[] = "WIA";
uint_t WiaModel::m_osMajorVersion = 6;

WiaModel::WiaModel( ImageSite* imageSite, MessageListener* messageListener )
	: m_pImageSite( imageSite ), m_messageListener( messageListener ),
	m_showDeviceUI( false ),
	m_deviceIndex( -1 ),
	m_pixelType( PixelType::Rgb ), m_colorDepth( 24 ), m_resolution( 100 ), m_autoBright( false ), m_contrast( 0 ), m_orientation( 0 ),
	m_imageTypeIntent( WIA_INTENT_IMAGE_TYPE_COLOR ),
	m_paperSource( PaperSource::None ),
	m_dataType( 0 ),
	m_pItemDevice( NULL ), m_pItemDevice2( NULL ), m_pItemRoot( NULL ), m_pItemRoot2( NULL ),
	m_devMgrInitialized( false ),
	m_devInitialized( false ),
	m_isFlatbedSupported( false ), m_isFeederSupported( false ), m_isDuplexSupported( false ), m_duplex( false ), m_isPaperSourceFixed( false ),
	m_pageWidth( 0 ), m_pageHeight( 0 ),
	m_pEventCallback( NULL ),
	m_connected( false ),
	m_maxHorizontalSize( 0 ), m_minHorizontalSize( 0 ), m_maxVerticalSize( 0 ), m_minVerticalSize( 0 ), m_opticalXres( 0 ), m_opticalYres( 0 )
{
	HRESULT hr;
	switch ( m_osMajorVersion )
	{
	case 5: hr = m_pWiaDevMgr.CoCreateInstance( CLSID_WiaDevMgr ); break;
	case 6: hr = m_pWiaDevMgr2.CoCreateInstance( CLSID_WiaDevMgr2 ); break; // 0x80040154(-2147221164) - class not registered
	default:
		_ASSERT( 0 );
	}

	if ( SUCCEEDED( hr ) )
	{
		m_devMgrInitialized = true;
	}
	else
	{
		m_pWiaDevMgr = NULL;
		m_pWiaDevMgr2 = NULL;
	}

	if ( m_devMgrInitialized )
	{
		m_pEventCallback = new CEventCallback( m_messageListener );

		// register the event callback interface

		hr = m_pWiaDevMgr2->RegisterEventCallbackInterface( 0, NULL, &WIA_EVENT_DEVICE_CONNECTED, m_pEventCallback, &m_pConnectEventObject );

		if ( FAILED( hr ) )
		{
			return;
		}

		hr = m_pWiaDevMgr2->RegisterEventCallbackInterface( 0, NULL, &WIA_EVENT_DEVICE_DISCONNECTED, m_pEventCallback, &m_pDisconnectEventObject );

		if ( FAILED( hr ) )
		{
			return;
		}
	}
}

WiaModel::~WiaModel()
{
	/*switch ( m_osMajorVersion )
	{
	case 5:
		m_pItemRoot->Release();
		m_pItemRoot = NULL;
		break;

	case 6:
		m_pItemRoot2->Release();
		m_pItemRoot2 = NULL;
		break;
	}*/
}

void WiaModel::setIdentity( char* productName )
{
}

bool WiaModel::getDevices()
{
	// reset the current device index
	m_deviceIndex = -1;
	// clear the devices
	m_devices.clear( false );

	if ( !m_devMgrInitialized ) return false;

	// get a device enumerator interface
	IEnumWIA_DEV_INFO *pWiaEnumDevInfo = NULL;
	HRESULT hr;

	switch ( m_osMajorVersion )
	{
	case 5: hr = m_pWiaDevMgr->EnumDeviceInfo( WIA_DEVINFO_ENUM_LOCAL, &pWiaEnumDevInfo ); break;
	case 6: hr = m_pWiaDevMgr2->EnumDeviceInfo( WIA_DEVINFO_ENUM_LOCAL, &pWiaEnumDevInfo ); break;
	}

	if ( SUCCEEDED( hr ) )
	{
		ULONG celt = 0;
		hr = pWiaEnumDevInfo->GetCount( &celt );
		ULONG deviceCount = celt;

		if ( deviceCount > 0 )
		{
			int deviceIndex = 0;

			// loop until you get an error or pWiaEnumDevInfo->Next() returns S_FALSE to signal the end of the list.
			while ( hr == S_OK )
			{
				// get the next device's property storage interface pointer
				IWiaPropertyStorage *pWiaPropertyStorage = NULL;
				hr = pWiaEnumDevInfo->Next( 1, &pWiaPropertyStorage, NULL );

				// pWiaEnumDevInfo->Next() will return S_FALSE when the list is exhausted, so check for S_OK before using the returned value.
				if ( hr == S_OK )
				{
					PROPSPEC propspec[] = { 
						{ 1, WIA_DIP_DEV_ID }, 
						{ 1, WIA_DIP_DEV_NAME}, 
						{ 1, WIA_DIP_VEND_DESC }, 
						{ 1, WIA_DIP_DEV_DESC } 
					};

					PROPVARIANT rgpropvar[ 4 ];
					::memset( &rgpropvar, 0, sizeof( rgpropvar ) );

					pWiaPropertyStorage->ReadMultiple( 4, propspec, rgpropvar );

					PROPVARIANT* propvar = NULL;
					DeviceInfo di = { 0 };


#define SETDEVINFO( i, prop ) propvar = rgpropvar + i; \
	if ( propvar->vt == VT_BSTR ) { ::memcpy( di.##prop, propvar->bstrVal, ( ::SysStringLen( propvar->bstrVal ) + 1 ) << 1 ); }

					di.index = deviceIndex + 1;

					SETDEVINFO( 0, id );

					//const wchar_t* pSeparator = ::wcschr( ( const wchar_t* ) di.id, L'\\' );
					//size_t uidLength = ( pSeparator - di.id );
					//memcpy( di.uid, di.id, uidLength << 1 );
					SETDEVINFO( 3, uid );

					SETDEVINFO( 1, productName );
					SETDEVINFO( 2, manufacturer );
					SETDEVINFO( 3, productFamily );

					ImagingDevice device( &di );

					m_devices.add( device );

#undef SETDEVINFO

					// release the device's IWiaPropertyStorage*
					pWiaPropertyStorage->Release();
					pWiaPropertyStorage = NULL;

					deviceIndex++;
				}
			}
		}

		// if the result of the enumeration is S_FALSE (which is normal), change it to S_OK
		if ( hr == S_FALSE )
		{
			hr = S_OK;
		}

		// release the enumerator
		pWiaEnumDevInfo->Release();
		pWiaEnumDevInfo = NULL;
	}
	return true;
}

long WiaModel::getImages()
{
	/*
	FEEDER SUPPORT:

	Windows XP: WIA_DPS_DOCUMENT_HANDLING_SELECT (set to FEEDER (obsolete for Vista/7)) for the root item, and use child item as programmable data source
	Root
	|_Scan Item

	Windows Vista/7: WIA_IPS_DOCUMENT_HANDLING_SELECT for the programmable data source
	Root
	|_Flatbed
	|_Feeder
	  |_
	    |_Front
	    |_Back


	TODO: figure out what WIA_DEVICE_DIALOG_SINGLE_IMAGE does

	*/

	HRESULT hr;

	CComPtr<IWiaDevMgr> pWiaDevMgr;
	CComPtr<IWiaDevMgr2> pWiaDevMgr2;

	switch ( m_osMajorVersion )
	{
	case 5: hr = pWiaDevMgr.CoCreateInstance( CLSID_WiaDevMgr ); break;
	case 6: hr = pWiaDevMgr2.CoCreateInstance( CLSID_WiaDevMgr2 ); break; // 0x80040154(-2147221164) - class not registered
	default:
		_ASSERT( 0 );
	}


	//if ( !m_devMgrInitialized ) return E_POINTER;

	// initialize the local root item variable with the supplied value
	// if no value is supplied, display the device selection common dialog

	CComPtr<IWiaItem> pItemRoot = NULL;
	CComPtr<IWiaItem2> pItemRoot2 = NULL;

	// display the device selection common dialog

	/*
	switch ( m_osMajorVersion )
	{
	case 5:
	hr = m_pWiaDevMgr->SelectDeviceDlg( NULL, StiDeviceTypeDefault, 0, 0, &pItemRoot ); // TODO: filter by device type, lFlags revise
	break;
	case 6:
	hr = m_pWiaDevMgr2->SelectDeviceDlg( NULL, StiDeviceTypeDefault, 0, 0, &pItemRoot2 ); // TODO: filter by device type, lFlags revise
	break;
	}

	if ( FAILED( hr ) || hr == S_FALSE )
	{
	return WIA_S_NO_DEVICE_AVAILABLE;
	}
	*/

	const DeviceInfo& currentDevice = m_devices[ m_deviceIndex ].getDeviceInfo();
	BSTR deviceID = ::SysAllocString( currentDevice.id );

	switch ( m_osMajorVersion )
	{
	case 5: hr = pWiaDevMgr->CreateDevice( deviceID, &pItemRoot ); break;
	case 6: hr =  pWiaDevMgr2->CreateDevice( 0, deviceID, &pItemRoot2 ); break;
	}

	::SysFreeString( deviceID );

	if ( FAILED( hr ) || hr == S_FALSE ) return hr;


	IWiaPropertyStorage* pItemRootProperties = NULL;

	switch ( m_osMajorVersion )
	{
	case 5:
		hr = pItemRoot->QueryInterface( IID_IWiaPropertyStorage, ( void** ) &pItemRootProperties );
		break;

	case 6:
		hr = pItemRoot2->QueryInterface( IID_IWiaPropertyStorage, ( void** ) &pItemRootProperties );
		break;
	}

	if ( hr )
	{
		return E_NOINTERFACE;
	}

	// display the image selection common dialog 
	CComPtr<IWiaItem> pItemDevice;
	CComPtr<IWiaItem2> pItemDevice2;


	if ( m_showDeviceUI )
	{
		switch ( m_osMajorVersion )
		{
		case 5:
			{
				CComPtrArray<IWiaItem> items;

				hr = pItemRoot->DeviceDlg( NULL, 0, WIA_INTENT_NONE, &items.Count(), &items );

				if ( FAILED( hr ) || hr == S_FALSE )
				{
					return hr;
				}
				pItemDevice = items[ 0 ];
			}
			break;

		case 6:
			{
				CComPtrArray<IWiaItem2> items2;

				hr = pItemRoot2->DeviceDlg( NULL, NULL, NULL, NULL, NULL, NULL, items2 );

				if ( FAILED( hr ) || hr == S_FALSE )
				{
					return hr;
				}
				pItemDevice2 = items2[ 0 ];
			}
			break;
		}
	}
	else
	{
		switch ( m_osMajorVersion )
		{
		case 5:
			{
				CComPtr<IEnumWiaItem> pEnumCategories;

				hr = pItemRoot->EnumChildItems( &pEnumCategories );

				if ( FAILED( hr ) || hr == S_FALSE )
				{
					return hr;
				}

				ULONG fetched = 0, count = 0;

				pEnumCategories->GetCount( &count );

				if( count > 0 )
				{
					pEnumCategories->Next( 1, &pItemDevice, &fetched );
				}
			}
			break;

		case 6:
			{
				CComPtr<IEnumWiaItem2> pEnumCategories;

				GUID categoryGuid = m_paperSource == PaperSource::Feeder ? WIA_CATEGORY_FEEDER : WIA_CATEGORY_FLATBED;

				hr = pItemRoot2->EnumChildItems( &categoryGuid, &pEnumCategories ); // if NULL then all child nodes are enumerated

				if ( FAILED( hr ) || hr == S_FALSE )
				{
					return hr;
				}

				ULONG fetched = 0, count = 0;

				pEnumCategories->GetCount( &count );

				if( count > 0 )
				{
					pEnumCategories->Next( 1, &pItemDevice2, &fetched );
				}
			}
			break;
		}
	}


	//-------------------------

	// for ADF scanners, the common dialog explicitly sets the page count to one
	// so in order to transfer multiple images, set the page count to ALL_PAGES if the WIA_DEVICE_DIALOG_SINGLE_IMAGE flag is not specified, 

	//if ( !( lFlags & WIA_DEVICE_DIALOG_SINGLE_IMAGE ) ) // determine whether the single image transfer was selected in UI

	if ( m_osMajorVersion == 5 )
	{
		PROPSPEC specDevType;

		specDevType.ulKind = PRSPEC_PROPID;
		specDevType.propid = WIA_DIP_DEV_TYPE;

		LONG nDevType;

		hr = ::ReadPropertyLong( pItemRootProperties, &specDevType, &nDevType );

		// check if the selected device is a scanner
		if ( SUCCEEDED( hr ) && ( GET_STIDEVICE_TYPE( nDevType ) == StiDeviceTypeScanner ) )
		{
			// set the document handling
			PROPSPEC specDocumentHandlingSelect;

			specDocumentHandlingSelect.ulKind = PRSPEC_PROPID;
			specDocumentHandlingSelect.propid = WIA_DPS_DOCUMENT_HANDLING_SELECT;

			PROPVARIANT varDocumentHandlingSelect;

			varDocumentHandlingSelect.vt = VT_I4;
			varDocumentHandlingSelect.intVal = m_paperSource == PaperSource::Feeder ? FEEDER : FLATBED; // sometimes FRONT_ONLY does not work // FEEDER | FRONT_ONLY
			// TODO:

			hr = pItemRootProperties->WriteMultiple( 1, &specDocumentHandlingSelect, &varDocumentHandlingSelect, WIA_DPS_FIRST );

			// determine if the document feeder is selected or not

			LONG documentHandlingSelect;

			hr = ::ReadPropertyLong( pItemRootProperties, &specDocumentHandlingSelect, &documentHandlingSelect );

			if ( SUCCEEDED( hr ) && ( documentHandlingSelect & FEEDER ) )
			{
				PROPSPEC specPages;

				specPages.ulKind = PRSPEC_PROPID;
				specPages.propid = WIA_DPS_PAGES;

				PROPVARIANT varPages;

				varPages.vt = VT_I4;
				varPages.lVal = ALL_PAGES;

				pItemRootProperties->WriteMultiple( 1, &specPages, &varPages, WIA_DPS_FIRST );

				::PropVariantClear( &varPages );
			}
		}
	}
	/*else if ( m_osMajorVersion == 6 )
	{
	if ( m_feederEnabled )
	{
	PROPSPEC specPages;

	specPages.ulKind = PRSPEC_PROPID;
	specPages.propid = WIA_IPS_PAGES;

	PROPVARIANT varPages;

	varPages.vt = VT_I4;
	varPages.lVal = ALL_PAGES;

	hr = pDeviceProperties->WriteMultiple( 1, &specPages, &varPages, WIA_IPS_FIRST );

	PropVariantClear( &varPages );
	}
	}*/

	// If a status callback function is not supplied, use the default.
	// The default displays a simple dialog with a progress bar and cancel button.

	/*
	CComPtr<CProgressDlg> pProgressDlg;

	if (pfnProgressCallback == NULL)
	{
	pfnProgressCallback = DefaultProgressCallback;

	pProgressDlg = new CProgressDlg(hWndParent);

	pProgressCallbackParam = (CProgressDlg *) pProgressDlg;
	}
	*/

	// create data callback interface

	// start the transfer of the selected items

	//for (int i = 0; i < ppIWiaItem.Count(); ++i)
	//for ( int i = 0; i < 1; ++i )
	//{
	// get the interface pointers

	//CComQIPtr<IWiaPropertyStorage> pDevicePropertyStorage( pItemDevice );
	IWiaPropertyStorage* pDevicePropertyStorage = NULL;
	IWiaDataTransfer* pDataTransfer = NULL;

	switch ( m_osMajorVersion )
	{
	case 5:
		if ( hr = pItemDevice->QueryInterface( IID_IWiaPropertyStorage, ( void** ) &pDevicePropertyStorage ) )
			return E_NOINTERFACE;

		break;

	case 6:
		if ( hr = pItemDevice2->QueryInterface( IID_IWiaPropertyStorage, ( void** ) &pDevicePropertyStorage ) )
			return E_NOINTERFACE;

		break;
	}

	/*if ( pDevicePropertyStorage == NULL )
	{
	return E_NOINTERFACE;
	}

	CComQIPtr<IWiaDataTransfer> pIWiaDataTransfer( pItemDevice );

	if ( pIWiaDataTransfer == NULL )
	{
	return E_NOINTERFACE;
	}*/

	// set properties

	PROPSPEC spec;
	spec.ulKind = PRSPEC_PROPID;

	PROPVARIANT var;
	var.vt = VT_I4;


	// set the transfer type

	spec.propid = WIA_IPA_TYMED;
	LONG tymed = 0;

	switch ( m_osMajorVersion )
	{
	case 5: tymed = TYMED_CALLBACK; break;
	case 6: tymed = TYMED_FILE; break;
	}

	var.lVal = tymed;

	hr = pDevicePropertyStorage->WriteMultiple( 1, &spec, &var, WIA_IPA_FIRST );

	if ( FAILED( hr ) ) return hr;


	// pixelType (color depth)

	spec.propid = WIA_IPS_CUR_INTENT;
	var.intVal = m_imageTypeIntent;
	hr = pDevicePropertyStorage->WriteMultiple( 1, &spec, &var, WIA_IPS_FIRST );

	/*spec.propid = WIA_IPA_DEPTH;
	var.intVal = m_colorDepth;
	hr = pWiaPropertyStorage->WriteMultiple( 1, &spec, &var, WIA_IPA_FIRST );*/

	spec.propid = WIA_IPA_DATATYPE;
	var.intVal = m_dataType;
	hr = pDevicePropertyStorage->WriteMultiple( 1, &spec, &var, WIA_IPA_FIRST );


	// resolution


	//spec.propid = WIA_IPS_PAGE_SIZE;
	//var.intVal = WIA_PAGE_CUSTOM;//WIA_PAGE_A4;
	//hr = pDevicePropertyStorage->WriteMultiple( 1, &spec, &var, WIA_IPS_FIRST );

	//spec.propid = WIA_IPS_PAGE_WIDTH;
	//var.intVal = 8267;//WIA_PAGE_A4;
	//hr = pDevicePropertyStorage->WriteMultiple( 1, &spec, &var, WIA_IPS_FIRST );

	//spec.propid = WIA_IPS_PAGE_HEIGHT;
	//var.intVal = 11692;//WIA_PAGE_A4;
	//hr = pDevicePropertyStorage->WriteMultiple( 1, &spec, &var, WIA_IPS_FIRST );


	var.intVal = m_resolution;

	spec.propid = WIA_IPS_XRES;
	hr = pDevicePropertyStorage->WriteMultiple( 1, &spec, &var, WIA_IPS_FIRST );
	spec.propid = WIA_IPS_YRES;
	hr = pDevicePropertyStorage->WriteMultiple( 1, &spec, &var, WIA_IPS_FIRST );

	spec.propid = WIA_DPS_PAGE_SIZE;
	var.intVal = WIA_PAGE_A4;//;WIA_PAGE_CUSTOM
	hr = pDevicePropertyStorage->WriteMultiple( 1, &spec, &var, WIA_DPS_FIRST );

	//spec.propid = WIA_IPS_PAGE_WIDTH;
	//var.intVal = 8267;//WIA_PAGE_A4;
	//hr = pDevicePropertyStorage->WriteMultiple( 1, &spec, &var, WIA_IPS_FIRST );

	//spec.propid = WIA_IPS_PAGE_HEIGHT;
	//var.intVal = 11692;//WIA_PAGE_A4;
	//hr = pDevicePropertyStorage->WriteMultiple( 1, &spec, &var, WIA_IPS_FIRST );

	spec.propid = WIA_IPA_DATATYPE;
	var.intVal = m_dataType;
	hr = pDevicePropertyStorage->WriteMultiple( 1, &spec, &var, WIA_IPA_FIRST );



	spec.propid = WIA_IPS_XEXTENT;
	var.intVal = ( int ) ( m_pageWidth * m_resolution );
	hr = pDevicePropertyStorage->WriteMultiple( 1, &spec, &var, WIA_IPS_FIRST );

	spec.propid = WIA_IPS_YEXTENT;
	var.intVal = ( int ) ( m_pageHeight * m_resolution );
	hr = pDevicePropertyStorage->WriteMultiple( 1, &spec, &var, WIA_IPS_FIRST );

	spec.propid = WIA_IPS_SHEET_FEEDER_REGISTRATION;
	var.intVal = CENTERED;
	hr = pDevicePropertyStorage->WriteMultiple( 1, &spec, &var, WIA_IPS_FIRST );

	spec.propid = WIA_IPS_CONTRAST;
	var.intVal = m_contrast;
	hr = pDevicePropertyStorage->WriteMultiple( 1, &spec, &var, WIA_IPS_FIRST );

	spec.propid = WIA_IPS_ORIENTATION;
	var.intVal = m_orientation;// == OrientationPortrait ? PORTRAIT : LANDSCAPE;
	hr = pDevicePropertyStorage->WriteMultiple( 1, &spec, &var, WIA_IPS_FIRST );


	//spec.propid = WIA_IPS_XEXTENT;//WIA_IPS_PAGE_SIZE;
	//hr = pDevicePropertyStorage->ReadMultiple( 1, &spec, &var );

	//spec.propid = WIA_IPS_YEXTENT;//WIA_IPS_PAGE_SIZE;
	//hr = pDevicePropertyStorage->ReadMultiple( 1, &spec, &var );


	//WIA_IPA_COMPRESSION

	if ( m_osMajorVersion == 6 && m_paperSource == PaperSource::Feeder )
	{
		spec.ulKind = PRSPEC_PROPID;
		spec.propid = WIA_IPS_PAGES;

		var.vt = VT_I4;
		var.lVal = ALL_PAGES;

		hr = pDevicePropertyStorage->WriteMultiple( 1, &spec, &var, WIA_IPS_FIRST );
	}



	::PropVariantClear( &var );

	if ( FAILED( hr ) )
	{
		return hr;
	}


	// set the transfer format

	PROPSPEC specFormat;

	specFormat.ulKind = PRSPEC_PROPID;
	specFormat.propid = WIA_IPA_FORMAT; // TODO: look into WIA_IPA_PREFERRED_FORMAT if it can be useful (read and compare to BMP) for not setting the BMP for each scan session

	PROPVARIANT varFormat;

	varFormat.vt = VT_CLSID;
	varFormat.puuid = ( CLSID* ) ::CoTaskMemAlloc( sizeof( CLSID ) );

	if ( varFormat.puuid == NULL )
	{
		return E_OUTOFMEMORY;
	}

	*varFormat.puuid = m_osMajorVersion == 5 ? WiaImgFmt_MEMORYBMP : WiaImgFmt_BMP;

	hr = pDevicePropertyStorage->WriteMultiple( 1, &specFormat, &varFormat, WIA_IPA_FIRST );

	::PropVariantClear( &varFormat );

	if ( FAILED( hr ) )
	{
		return hr;
	}

	// read the transfer buffer size from the device, default to 64K

	PROPSPEC specBufferSize;

	specBufferSize.ulKind = PRSPEC_PROPID;
	specBufferSize.propid = WIA_IPA_BUFFER_SIZE;

	LONG bufferSize;

	hr = ::ReadPropertyLong( pDevicePropertyStorage, &specBufferSize, &bufferSize );

	if ( FAILED( hr ) )
	{
		bufferSize = 1 << 16; //64 * 1024;
	}

	// choose double buffered transfer for better performance

	switch ( m_osMajorVersion )
	{
	case 5:
		{
			// create the data callback interface instance
			LONG count;
			CComPtr<CWiaDataCallback> pDataCallback = new CWiaDataCallback( this->m_pImageSite, NULL, NULL, &count );

			if ( pDataCallback == NULL )
			{
				return E_OUTOFMEMORY;
			}

			CComQIPtr<IWiaDataTransfer> pDataTransfer( pItemDevice );

			//if ( hr = pItemDevice->QueryInterface( , ( void** ) &pDataTransfer ) )
			//	return E_NOINTERFACE;

			if ( pDataTransfer == NULL ) return E_NOINTERFACE;

			WIA_DATA_TRANSFER_INFO dataTransferInfo = { 0 };

			dataTransferInfo.ulSize = sizeof( WIA_DATA_TRANSFER_INFO );
			dataTransferInfo.ulBufferSize = bufferSize << 1;
			dataTransferInfo.bDoubleBuffer = TRUE;

			// start the transfer

			hr = pDataTransfer->idtGetBandedData( &dataTransferInfo, pDataCallback );
		}
		break;

	case 6:
		{
			CComPtr<IWiaItem2> pItemTransfer = NULL;

			if ( m_paperSource == PaperSource::Feeder )
			{
				CComPtr<IEnumWiaItem2> pEnum = NULL;

				pItemDevice2->EnumChildItems( NULL, &pEnum ); //WIA_CATEGORY_FEEDER_FRONT
				//if ( !pEnum ) // simple feeder
				//{
					pItemTransfer = pItemDevice2;
				//}
				//else // advanced feeder
				//{
				//	ULONG count = 0, fetched = 0;
				//	pEnum->GetCount( &count );
				//	if ( count > 0 )
				//	{
				//		pEnum->Next( 1, &pItemTransfer, &fetched );
				//	}
				//}
			}
			else // flatbed
			{
				pItemTransfer = pItemDevice2;
			}

			//CComPtr<IWiaTransfer> pDataTransfer( pItemDevice );
			CComQIPtr<IWiaTransfer> pTransfer( pItemTransfer );

			//if ( hr = pItemTransfer->QueryInterface( IID_IWiaTransfer, ( void** ) &pTransfer ) )
			if ( pTransfer == NULL )
			{
				return E_NOINTERFACE;//hr;
			}

			LONG lItemType = 0;
			hr = pItemTransfer->GetItemType( &lItemType );

			//download all items which have WiaItemTypeTransfer flag set
			LONG l = lItemType & WiaItemTypeTransfer;


			// create transfer callback interface instance

			//CComPtr<CWiaTransferCallback> pTransferCallback = new CWiaTransferCallback( this->m_pImageSite );
			CWiaTransferCallback* pTransferCallback = new CWiaTransferCallback( this->m_pImageSite );

			if ( pTransferCallback == NULL )
			{
				return E_OUTOFMEMORY;
			}

			hr = pTransfer->Download( 0, pTransferCallback );
		}
		break;
	}

	if ( FAILED( hr ) || hr == S_FALSE )
	{
		return hr;
	}

	pDevicePropertyStorage->Release();

	//}

	pItemRootProperties->Release();

	return S_OK;
}

ResultCode WiaModel::scan()
{
	::CoInitialize( NULL );

	getImages();

	::CoUninitialize();

	return ResultCode::Success;
}

void WiaModel::setPixelType( PixelType value )
{
	m_pixelType = value;
	switch ( m_pixelType )
	{
	case PixelType::Rgb:
		m_colorDepth = 24;
		m_imageTypeIntent = WIA_INTENT_IMAGE_TYPE_COLOR;
		m_dataType = WIA_DATA_COLOR;
		break;

	case PixelType::Gray:
		m_colorDepth = 8;
		m_imageTypeIntent = WIA_INTENT_IMAGE_TYPE_GRAYSCALE;
		m_dataType = WIA_DATA_GRAYSCALE;
		break;

	case PixelType::Bw:
		m_colorDepth = 1;
		m_imageTypeIntent = WIA_INTENT_IMAGE_TYPE_GRAYSCALE;
		m_dataType = WIA_DATA_THRESHOLD;
		break;
	}
}

bool WiaModel::connect()
{
	if ( m_connected ) return true;

	HRESULT hr;

	const DeviceInfo& currentDevice = m_devices[ m_deviceIndex ].getDeviceInfo();
	BSTR deviceID = ::SysAllocString( currentDevice.id );

	switch ( m_osMajorVersion )
	{
	case 5:
		{
			//CComPtr<IWiaItem> pItemRoot;

			/*
			// display the device selection common dialog
			hr = m_pWiaDevMgr->SelectDeviceDlg( NULL, StiDeviceTypeDefault, 0, 0, &pItemRoot ); // TODO: filter by device type, lFlags revise
			*/

			if ( !m_pItemRoot )
			{
				hr = m_pWiaDevMgr->CreateDevice( deviceID, &m_pItemRoot );

				if ( FAILED( hr ) || hr == S_FALSE ) return false;
			}

			if ( m_pItemDevice == NULL )
			{
				CComPtr<IEnumWiaItem> pEnumDevices;

				hr = m_pItemRoot->EnumChildItems( &pEnumDevices );

				if ( SUCCEEDED( hr ) )
				{
					ULONG fetched = 0, count = 0;

					pEnumDevices->GetCount( &count );
					if( count > 0 )
					{
						pEnumDevices->Next( 1, &m_pItemDevice, &fetched );

						m_devInitialized = true;
					}
				}
			}
		}
		break;
	case 6:
		{
			//CComPtr<IWiaItem2> pItemRoot2;

			// display the device selection common dialog

			//hr = m_pWiaDevMgr2->SelectDeviceDlg( NULL, StiDeviceTypeDefault, 0, 0, &pItemRoot2 ); // TODO: filter by device type, lFlags revise

			if ( !m_pItemRoot2 )
			{
				hr = m_pWiaDevMgr2->CreateDevice( 0, deviceID, &m_pItemRoot2 );

				if ( FAILED( hr ) || hr == S_FALSE ) return false;
			}

			CComPtr<IEnumWiaItem2> pEnumDevices;

			hr = m_pItemRoot2->EnumChildItems( NULL, &pEnumDevices );

			if ( FAILED( hr ) || hr == S_FALSE )
			{
				return false; //hr;
			}

			ULONG count = 0;

			pEnumDevices->GetCount( &count );

			if( count > 0 )
			{	
				m_devInitialized = true;

				m_connected = true;
			}
		}
		break;
	}

	SysFreeString( deviceID );
	return m_connected;
}

void WiaModel::disconnect()
{
	if ( !m_connected ) return;

	switch ( m_osMajorVersion )
	{
	case 5:
		m_pItemDevice->Release();
		m_pItemDevice = NULL;

		m_pItemRoot->Release();
		m_pItemRoot = NULL;
		break;

	case 6:
		if ( m_pItemDevice2 )
		{
			m_pItemDevice2->Release();
			m_pItemDevice2 = NULL;
		}
		if ( m_pItemRoot2 )
		{
			m_pItemRoot2->Release();
			m_pItemRoot2 = NULL;
		}
		break;
	}
	m_connected = false;
}

//
// WIA_DPS_DOCUMENT_HANDLING_CAPABILITIES flags
//
//
//#define  FEED                          0x01
//#define  FLAT                          0x02
//#define  DUP                           0x04
//#define  DETECT_FLAT                   0x08
//#define  DETECT_SCAN                   0x10
//#define  DETECT_FEED                   0x20
//#define  DETECT_DUP                    0x40
//#define  DETECT_FEED_AVAIL             0x80
//#define  DETECT_DUP_AVAIL              0x100
//#if (_WIN32_WINNT >= 0x0600)
//#define  FILM_TPA                      0x200
//#define  DETECT_FILM_TPA               0x400
//#define  STOR                          0x800
//#define  DETECT_STOR                   0x1000
//#define  ADVANCED_DUP                  0x2000
//#define  AUTO_SOURCE                   0x8000

BaseCap* WiaModel::getCap( uint_t capId )
{
	if ( !m_devMgrInitialized || !m_devInitialized ) return NULL;

	if ( !m_connected ) return NULL;

	HRESULT hr;

	bool itemInitialized = false;

	switch ( m_osMajorVersion )
	{
	case 5:

		break;

	case 6:

		if ( !m_pItemDevice2 )
		{
			CComPtr<IEnumWiaItem2> pEnumItems;

			GUID categoryGuid = m_paperSource == PaperSource::Feeder ? WIA_CATEGORY_FEEDER : WIA_CATEGORY_FLATBED;

			hr = m_pItemRoot2->EnumChildItems( &categoryGuid, &pEnumItems ); // if NULL then all child nodes are enumerated

			if ( FAILED( hr ) || hr == S_FALSE )
			{
				return NULL;
			}

			if ( SUCCEEDED( hr ) )
			{
				ULONG fetched = 0, count = 0;

				pEnumItems->GetCount( &count );

				if( count > 0 )
				{
					pEnumItems->Next( 1, &m_pItemDevice2, &fetched );

					itemInitialized = true;
				}
			}
		}
		else
		{
			itemInitialized = true;
		}

		break;
	}

	if ( !itemInitialized ) return NULL;

	BaseCap* baseCap = NULL;

	IWiaPropertyStorage* pDeviceProps = NULL;
	switch ( m_osMajorVersion )
	{
	case 5: hr = m_pItemDevice->QueryInterface( IID_IWiaPropertyStorage, ( void** ) &pDeviceProps ); break;
	case 6: hr = m_pItemDevice2->QueryInterface( IID_IWiaPropertyStorage, ( void** ) &pDeviceProps ); break;
	}

	if ( hr ) return NULL;

	PROPSPEC spec = { 0 };
	spec.ulKind = PRSPEC_PROPID;
	spec.propid = capId;

	PROPVARIANT var = { 0 };

	ULONG flags = 0;

	hr = pDeviceProps->GetPropertyAttributes( 1, &spec, &flags, &var );

	if ( FAILED( hr ) || hr == S_FALSE )
	{
		DWORD dw = ::GetLastError();
		return NULL;
	}

	ULONG flagNone = flags & WIA_PROP_NONE;
	ULONG flagRange = flags & WIA_PROP_RANGE;
	ULONG flagList = flags & WIA_PROP_LIST;
	ULONG flagRead = flags & WIA_PROP_READ;
	ULONG flagWrite = flags & WIA_PROP_WRITE;
	ULONG flagSync = flags & WIA_PROP_SYNC_REQUIRED;
	ULONG flagFlag = flags & WIA_PROP_FLAG;

	ULONG itemType = VT_I4; // TODO:

	if ( flagFlag ) // one value
	{

	}
	else if ( flagList ) // list
	{
		switch ( itemType )
		{
		case VT_I4:
		case VT_UI4:
			{
				CAUL& values = var.caul;
				ULONG count = values.pElems[ WIA_LIST_COUNT ];
				int* array = new int[ count ];

				::memcpy( array, values.pElems + WIA_LIST_VALUES, count * sizeof( ULONG ) );

				baseCap = new IntListCap( capId, count, array );

				break;
			}
		default:
			//assert( 0 );
			break;
		}
	}
	else if ( flagRange ) // range
	{
		switch ( itemType )
		{
		case VT_I4:
		case VT_UI4:
			{
				CAUL& values = var.caul;
				ULONG max = values.pElems[ WIA_RANGE_MAX ];
				ULONG min = values.pElems[ WIA_RANGE_MIN ];
				ULONG step = values.pElems[ WIA_RANGE_STEP ];
				ULONG defaultValue = values.pElems[ WIA_RANGE_NOM ];

				baseCap = new IntRangeCap( capId, min, max, step, defaultValue, -1 );

				break;
			}
		default:
			//assert( 0 );
			break;
		}
	}

	pDeviceProps->Release();

	return baseCap;
}

bool WiaModel::getIsDeviceFeatureSupported( DeviceFeature deviceFeature ) const
{
	switch ( deviceFeature )
	{
	case DeviceFeature::Flatbed: return m_isFlatbedSupported; break;
	case DeviceFeature::Feeder: return m_isFeederSupported; break;
	case DeviceFeature::Duplex: return m_isDuplexSupported; break;
	}
	return false;
}

bool readInt2( IWiaPropertyStorage* ps, PROPID prop1, PROPID prop2, int& prop1Value, int& prop2Value )
{
	prop1Value = prop2Value = 0;

	PROPSPEC rgpspec[] = { { 1, prop1 }, { 1, prop2} };

	PROPVARIANT rgpropvar[ 2 ] = { 0 };

	HRESULT hr = ps->ReadMultiple( 2, rgpspec, rgpropvar );

	if ( hr != S_OK ) return false;

	prop1Value = rgpropvar[ 0 ].intVal;
	prop1Value = rgpropvar[ 1 ].intVal;

	return true;
}

bool readInt1( IWiaPropertyStorage* ps, PROPID prop1, int& prop1Value )
{
	prop1Value = 0;

	PROPSPEC rgpspec = { 1, prop1 };

	PROPVARIANT rgpropvar = { 0 };

	HRESULT hr = ps->ReadMultiple( 1, &rgpspec, &rgpropvar );

	if ( hr != S_OK ) return false;

	prop1Value = rgpropvar.intVal;

	return true;
}

// the method must be called after the device is selected
long WiaModel::initializeDevice()
{
	/*
	FEEDER SUPPORT:

	Windows XP: WIA_DPS_DOCUMENT_HANDLING_SELECT (set to FEEDER (obsolete for Vista/7)) for the root item, and use child item as programmable data source
	Root
	|_Scan Item

	Windows Vista/7: WIA_IPS_DOCUMENT_HANDLING_SELECT for the programmable data source
	Root
	|_Flatbed
	|_Feeder
	|_Front
	|_Back

	*/

	bool connected = false;
	if ( !m_connected )
	{
		this->connect();
		connected = true;
	}

	m_isFeederSupported = m_isFlatbedSupported = m_isDuplexSupported = false;

	HRESULT hr;

	IWiaPropertyStorage* pItemRootProperties = NULL;

	switch ( m_osMajorVersion )
	{
	case 5: hr = m_pItemRoot->QueryInterface( IID_IWiaPropertyStorage, ( void** ) &pItemRootProperties ); break;
	case 6: hr = m_pItemRoot2->QueryInterface( IID_IWiaPropertyStorage, ( void** ) &pItemRootProperties ); break;
	}

	if ( hr )
	{
		//return E_NOINTERFACE;
		goto end;
	}	

	switch ( m_osMajorVersion )
	{
	case 5:
		{
			/*CComPtr<IEnumWiaItem> pEnumCategories;

			hr = pItemRoot->EnumChildItems( &pEnumCategories );

			if ( FAILED( hr ) || hr == S_FALSE )
			{
				return hr;
			}

			ULONG fetched = 0, count = 0;

			pEnumCategories->GetCount( &count );

			if( count > 0 )
			{
				pEnumCategories->Next( 1, &pItemDevice, &fetched );
			}*/
		}
		break;

	case 6:
		{
			m_paperSource = PaperSource::None;
			m_isFlatbedSupported = m_isFeederSupported = m_isDuplexSupported = false;
			m_isPaperSourceFixed = false;

			CComPtr<IEnumWiaItem2> pEnumItems;
			IWiaItem2* pItem = NULL;

			//TODO: this principle can be used if the WIA_DPS_DOCUMENT_HANDLING_CAPABILITIES fails

			hr = m_pItemRoot2->EnumChildItems( NULL, &pEnumItems ); // if category is NULL then all child nodes are enumerated

			if ( hr )
			{
				//return hr;
				goto end;
			}

			ULONG fetched = 0, count = 0;

			pEnumItems->GetCount( &count );

			if ( count > 0 )
			{
				while ( pEnumItems->Next( 1, &pItem, &fetched ) == S_OK )
				{
					GUID guid = { 0 };
					pItem->GetItemCategory( &guid );

					if ( !::memcmp( &WIA_CATEGORY_FLATBED, &guid, sizeof( GUID ) ) )
					{
						m_isFlatbedSupported = true;
					}
					else if ( !::memcmp( &WIA_CATEGORY_FEEDER, &guid, sizeof( GUID ) ) )
					{
						m_isFeederSupported = true;

						IWiaPropertyStorage* pItemProperties = NULL;

						hr = pItem->QueryInterface( IID_IWiaPropertyStorage, ( void** ) &pItemProperties );

						// try all document handling to determine device's capabilities
						PROPSPEC specDocumentHandlingSelect = { 0 };
						specDocumentHandlingSelect.ulKind = PRSPEC_PROPID;
						specDocumentHandlingSelect.propid = WIA_IPS_DOCUMENT_HANDLING_SELECT;

						PROPVARIANT varDocumentHandlingSelect = { 0 };
						varDocumentHandlingSelect.vt = VT_I4;
						//varDocumentHandlingSelect.intVal = FRONT_ONLY;//DUPLEX; // sometimes FRONT_ONLY does not work // FEEDER | FRONT_ONLY
						varDocumentHandlingSelect.intVal = FEEDER | DUPLEX;

						hr = pItemProperties->WriteMultiple( 1, &specDocumentHandlingSelect, &varDocumentHandlingSelect, WIA_IPS_FIRST );

						if ( hr != S_OK )
						{
							m_isDuplexSupported = false;
						}

						pItemProperties->Release(); //needed ?
					}
					
					pItem->Release();
				}

				if ( ( m_isFlatbedSupported ? 1 : 0 ) + ( m_isFeederSupported ? 1 : 0 ) == 1 )
				{
					m_paperSource = m_isFlatbedSupported ? PaperSource::Flatbed : PaperSource::Feeder;
					m_isPaperSourceFixed = true;
				}

				/*IWiaPropertyStorage* pItemProperties = NULL;
				hr = pItem->QueryInterface( IID_IWiaPropertyStorage, ( void** ) &pItemProperties );

				::readInt1( pItemProperties, WIA_IPS_MAX_HORIZONTAL_SIZE, m_maxHorizontalSize );
				::readInt1( pItemProperties, WIA_IPS_MAX_VERTICAL_SIZE, m_maxVerticalSize );
				::readInt1( pItemProperties, WIA_IPS_MIN_HORIZONTAL_SIZE, m_minHorizontalSize );
				::readInt1( pItemProperties, WIA_IPS_MIN_VERTICAL_SIZE, m_minVerticalSize );
				::readInt2( pItemProperties, WIA_IPS_OPTICAL_XRES, WIA_IPS_OPTICAL_YRES, m_opticalXres, m_opticalYres );

				pItemProperties->Release();*/

			}
		}
		break;
	}

end:
	if ( connected )
	{
		this->disconnect();
	}

	return hr;
}

void WiaModel::setPaperSource( PaperSource value )
{
	if ( ( value == PaperSource::Flatbed && this->m_isFlatbedSupported ) || ( value == PaperSource::Feeder && this->m_isFeederSupported ) )
	{
		m_paperSource = value;
	}
}

ImagingDevice* WiaModel::getDevice( int index )
{
	if ( index < 0 || index >= m_devices.count() ) return NULL;
	return &m_devices[ index ];
}

void WiaModel::setDeviceIndex( int value )
{
	int intValue = ( int ) value;
	intValue--;
	m_deviceIndex = intValue >= 0 && intValue < m_devices.count() ? intValue : -1;
}

int WiaModel::getDeviceCount()
{
	 return m_devices.count();
}
